package com.unittec.zk.provider.config;

import java.text.SimpleDateFormat;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.TimeZone;

import com.unittec.zk.provider.common.RedisUtils;
import com.unittec.zk.provider.scope.ThreadLocalScope;
import org.apache.commons.lang3.StringUtils;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.support.converter.SerializerMessageConverter;
import org.springframework.cache.CacheManager;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.context.annotation.Scope;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.util.CollectionUtils;
import org.springframework.web.client.RestTemplate;

import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import com.baomidou.mybatisplus.core.parser.ISqlParser;
import com.baomidou.mybatisplus.extension.parsers.BlockAttackSqlParser;
import com.baomidou.mybatisplus.extension.plugins.OptimisticLockerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
import com.baomidou.mybatisplus.extension.plugins.tenant.TenantHandler;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
import com.google.common.collect.Lists;
import com.unittec.zk.provider.db.MetaObjectHandlerImpl;
import com.unittec.zk.provider.interceptor.TenantSqlParserExt;

import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.StringValue;
import net.sf.jsqlparser.statement.delete.Delete;

/**
 * @author wuxinwei
 */
@Configuration
@MapperScan("com.unittec.zk.provider.*.mapper*")
public class ProviderRootConfig {

    private static final List<String> igore_tbl = Lists.newArrayList("sys_servicerouter", "sys_application");

    @Bean
    public PaginationInterceptor paginationInterceptor() {
        PaginationInterceptor paginationInterceptor = new PaginationInterceptor();

        List<ISqlParser> sqlParserList = new ArrayList<>();
        // 机构信息
        TenantSqlParserExt tenantSqlParser = new TenantSqlParserExt();
        tenantSqlParser.setTenantHandler(new TenantHandler() {
            @Override
            public Expression getTenantId(boolean where) {
                return new StringValue(ThreadLocalScope.getAuthData().getTenantId());
            }

            @Override
            public String getTenantIdColumn() {
                return "tenant_id";
            }

            @Override
            public boolean doTableFilter(String tableName) {
                return igore_tbl.contains(tableName);
            }
        });

        // 应用信息
        TenantSqlParserExt appSqlParser = new TenantSqlParserExt();
        appSqlParser.setTenantHandler(new TenantHandler() {
            @Override
            public Expression getTenantId(boolean where) {
                return new StringValue(ThreadLocalScope.getAuthData().getAppId());
            }

            @Override
            public String getTenantIdColumn() {
                return "app_id";
            }

            @Override
            public boolean doTableFilter(String tableName) {
                // 对于一个机构有多个应用的情况，客户管理员登录系统成功后，应用编号设置为空
                // 只有管理员选择特定的应用后，把应用APP编码带上再做鉴权
                return igore_tbl.contains(tableName) || StringUtils.isBlank(ThreadLocalScope.getAuthData().getAppId());
            }
        });

        // 攻击 SQL 阻断解析器、加入解析链
        BlockAttackSqlParser blockAttackSqlParser = new BlockAttackSqlParser() {
            @Override
            public void processDelete(Delete delete) {
                // 如果你想自定义做点什么，可以重写父类方法像这样子
                // if ("user".equals(delete.getTable().getName())) {
                // // 自定义跳过某个表，其他关联表可以调用 delete.getTables() 判断
                // return;
                // }
                super.processDelete(delete);
            }
        };
        // sqlParserList.add(tenantSqlParser);
        // sqlParserList.add(appSqlParser);
        sqlParserList.add(blockAttackSqlParser);
        // 多租户的SQL解析器
        paginationInterceptor.setSqlParserList(sqlParserList);
        return paginationInterceptor;
    }

    /**
     * @return
     */
    @Bean
    public MetaObjectHandler metaObjectHandler() {
        return new MetaObjectHandlerImpl();
    }

    /**
     * 乐观锁插件
     *
     * @return
     */
    @Bean
    public OptimisticLockerInterceptor optimisticLockerInterceptor() {
        return new OptimisticLockerInterceptor();
    }

    /**
     * 由于需要依赖数据库表，有的服务不需要支持定时任务，这里只提供样例，由各个服务自己参考集成即可
     * <p>
     * ShedLock锁，解决定时任务在集群环境下的互斥运行，将任务的运行记录在数据库的shedlock表中
     * <p>
     * CREATE TABLE shedlock( name VARCHAR(64), lock_until TIMESTAMP(3) NULL, locked_at TIMESTAMP(3) NULL, locked_by
     * VARCHAR(255), PRIMARY KEY (name) )
     * <p>
     * private static final int FOURTEEN_MIN = 1 * 60 * 1000;// 1分钟 // @Scheduled(cron = "") //@SchedulerLock(name =
     * "scheduledTaskName", lockAtMostFor = FOURTEEN_MIN, lockAtLeastFor = FOURTEEN_MIN) //public void scheduledTask() {
     * // do something //}
     */

    // @Autowired
    // DataSource dataSource;
    // @Bean
    // public LockProvider lockProvider () {
    // return new JdbcTemplateLockProvider(dataSource);
    // }
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(redisConnectionFactory);

        Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer =
            new Jackson2JsonRedisSerializer<>(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);

        // 设置值（value）的序列化采用Jackson2JsonRedisSerializer。
        redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
        // 设置键（key）的序列化采用StringRedisSerializer。
        redisTemplate.setKeySerializer(new StringRedisSerializer());

        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }

    @Primary
    @Bean
    public CacheManager cacheManager(RedisConnectionFactory factory) {
        RedisSerializer<String> redisSerializer = new StringRedisSerializer();
        Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer =
            new Jackson2JsonRedisSerializer<>(Object.class);

        // 解决查询缓存转换异常的问题
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);

        // 配置序列化（解决乱码的问题）
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig(); // 生成一个默认配置，通过config对象即可对缓存进行自定义配置
        config = config.entryTtl(Duration.ofMinutes(15)) // 设置缓存的默认过期时间，也是使用Duration设置
            .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))
            .serializeValuesWith(
                RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer));

        // // 设置一个初始化的缓存空间set集合
        // Set<String> cacheNames = new HashSet<>();
        // cacheNames.add("ConfigCache");
        //
        // // 对每个缓存空间应用不同的配置
        // Map<String, RedisCacheConfiguration> configMap = new HashMap<>();
        // configMap.put("ConfigCache", config);

        RedisCacheManager cacheManager = RedisCacheManager.builder(factory) // 使用自定义的缓存配置初始化一个cacheManager
            // .initialCacheNames(cacheNames) // 注意这两句的调用顺序，一定要先调用该方法设置初始化的缓存名，再初始化相关的配置
            // .withInitialCacheConfigurations(configMap)
            .cacheDefaults(config).build();

        return cacheManager;
    }

    @Bean
    public CacheManager cacheManagerByOneDay(RedisConnectionFactory factory) {
        RedisSerializer<String> redisSerializer = new StringRedisSerializer();
        Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer =
                new Jackson2JsonRedisSerializer<>(Object.class);

        // 解决查询缓存转换异常的问题
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);

        // 配置序列化（解决乱码的问题）
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig(); // 生成一个默认配置，通过config对象即可对缓存进行自定义配置
        config = config.entryTtl(Duration.ofMinutes(1500)) // 设置缓存的默认过期时间，也是使用Duration设置
                .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))
                .serializeValuesWith(
                        RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer));

        // // 设置一个初始化的缓存空间set集合
        // Set<String> cacheNames = new HashSet<>();
        // cacheNames.add("ConfigCache");
        //
        // // 对每个缓存空间应用不同的配置
        // Map<String, RedisCacheConfiguration> configMap = new HashMap<>();
        // configMap.put("ConfigCache", config);

        RedisCacheManager cacheManager = RedisCacheManager.builder(factory) // 使用自定义的缓存配置初始化一个cacheManager
                // .initialCacheNames(cacheNames) // 注意这两句的调用顺序，一定要先调用该方法设置初始化的缓存名，再初始化相关的配置
                // .withInitialCacheConfigurations(configMap)
                .cacheDefaults(config).build();

        return cacheManager;
    }

    @Bean
    public RedisUtils getRedisUtils() {
        return new RedisUtils();
    }

    @Bean
    @LoadBalanced
    public RestTemplate getRestTemplate() {
        SimpleClientHttpRequestFactory httpRequestFactory = new SimpleClientHttpRequestFactory();
        httpRequestFactory.setReadTimeout(60000);
        httpRequestFactory.setConnectTimeout(60000);
        RestTemplate restTemplate = new RestTemplate(httpRequestFactory);

        List<HttpMessageConverter<?>> converterList = restTemplate.getMessageConverters();
        if (!CollectionUtils.isEmpty(converterList)) {
            for (HttpMessageConverter<?> converterTarget : converterList) {
                if (converterTarget.getClass() == MappingJackson2HttpMessageConverter.class) {
                    ObjectMapper objectMapper =
                        ((MappingJackson2HttpMessageConverter)converterTarget).getObjectMapper();
                    objectMapper.setPropertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE);
                    objectMapper.setTimeZone(TimeZone.getTimeZone("GMT+8"));
                    objectMapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
                    objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
                    objectMapper.configure(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL, true);
                    break;
                }
            }
        }
        return restTemplate;
    }

    /**
     *
     * @param connectionFactory
     * @return
     */
    @Bean
    @Scope("prototype")
    public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
        RabbitTemplate template = new RabbitTemplate(connectionFactory);
        template.setMandatory(true);
        template.setMessageConverter(new SerializerMessageConverter());
        return template;
    }

}
